package com.xuecheng.orders.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.base.utils.IdWorkerUtils;
import com.xuecheng.base.utils.QRCodeUtil;
import com.xuecheng.messagesdk.model.po.MqMessage;
import com.xuecheng.messagesdk.service.MqMessageService;
import com.xuecheng.orders.config.AlipayConfig;
import com.xuecheng.orders.config.PayNotifyConfig;
import com.xuecheng.orders.mapper.XcOrdersGoodsMapper;
import com.xuecheng.orders.mapper.XcOrdersMapper;
import com.xuecheng.orders.mapper.XcPayRecordMapper;
import com.xuecheng.orders.model.dto.AddOrderDto;
import com.xuecheng.orders.model.dto.PayRecordDto;
import com.xuecheng.orders.model.dto.PayStatusDto;
import com.xuecheng.orders.model.po.XcOrders;
import com.xuecheng.orders.model.po.XcOrdersGoods;
import com.xuecheng.orders.model.po.XcPayRecord;
import com.xuecheng.orders.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final XcOrdersMapper ordersMapper; // 订单表

    private final XcOrdersGoodsMapper ordersGoodsMapper; // 订单明细表

    private final XcPayRecordMapper payRecordMapper;

    private final RabbitTemplate rabbitTemplate;

    private final MqMessageService mqMessageService;

    @Autowired
    // 非事务方法调用事务方法需要使用代理对象去调用才可以保证事务不失效
    // 怎么实现 就是 自己注入自己
    private OrderServiceImpl currentProxy;

    @Value("${pay.qrcodeurl}")
    String qrodeurl;

    @Value("${pay.alipay.APP_ID}")
    String APP_ID;

    @Value("${pay.alipay.APP_PRIVATE_KEY}")
    String APP_PRIVATE_KEY;

    @Value("${pay.alipay.ALIPAY_PUBLIC_KEY}")
    String ALIPAY_PUBLIC_KEY;

    /**
     * 创建商品订单
     *
     * @param userId      用户id
     * @param addOrderDto 订单信息
     * @return 支付记录(包括二维码)
     */
    @Transactional
    @Override
    public PayRecordDto createOrder(String userId, AddOrderDto addOrderDto) {
        // 插入订单 订单主表 订单明细表
        XcOrders xcOrders = saveXcOrders(userId, addOrderDto);

        // 插入支付记录
        XcPayRecord payRecord = createPayRecord(xcOrders);
        Long payNo = payRecord.getPayNo();

        // 生成二维码
        QRCodeUtil qrCodeUtil = new QRCodeUtil();
        // 生成支付二维码的url
        String url = String.format(qrodeurl, payNo);
        // 生成二维码的图片
        String qrCode = null;
        try {
            qrCode = qrCodeUtil.createQRCode(url, 200, 200);
        } catch (IOException e) {
            XueChengPlusException.cast("生成二维码出错");
        }

        // 构建返回参数
        PayRecordDto payRecordDto = new PayRecordDto();
        BeanUtils.copyProperties(payRecord, payRecordDto);
        payRecordDto.setQrcode(qrCode);

        return payRecordDto;
    }

    /**
     * 根据支付记录号查询支付记录
     *
     * @param payNo 支付记录号
     * @return 支付记录对象
     */
    @Override
    public XcPayRecord getPayRecordByPayNo(String payNo) {
        return payRecordMapper.selectOne(new LambdaQueryWrapper<XcPayRecord>().eq(XcPayRecord::getPayNo, payNo));
    }

    /**
     * 请求支付宝查询支付结果
     *
     * @param payNo 支付记录id
     * @return 支付记录信息
     */
    @Override
    public PayRecordDto queryPayResult(String payNo) {
        // 调用支付宝的接口查询支付结果
        PayStatusDto payStatusDto = queryPayResultFromAlipay(payNo);

        // 拿到支付结果更新支付记录表和订单表的支付状态
        currentProxy.saveAliPayStatus(payStatusDto);

        // 拿到最新的支付记录信息
        XcPayRecord payRecordByPayNo = getPayRecordByPayNo(payNo);
        PayRecordDto payRecordDto = new PayRecordDto();
        BeanUtils.copyProperties(payRecordByPayNo, payRecordDto);

        return payRecordDto;
    }

    /**
     * 请求支付宝查询支付结果
     *
     * @param payNo 支付交易号
     * @return 支付结果
     */
    public PayStatusDto queryPayResultFromAlipay(String payNo) {
        // 调用支付宝的接口查询支付结果
        AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, "json", AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE); // 获得初始化的AlipayClient
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", payNo);
        // bizContent.put("trade_no", "2014112611001004680073956707");
        request.setBizContent(bizContent.toString());
        // 支付宝的信息
        String body = null;
        try {
            AlipayTradeQueryResponse response = alipayClient.execute(request);
            if (!response.isSuccess()) {
                XueChengPlusException.cast("调用支付宝查询接口失败");
            }
            body = response.getBody();
        } catch (AlipayApiException e) {
            e.printStackTrace();
            XueChengPlusException.cast("调用支付宝查询接口失败");
        }

        Map<String, String> bodyMap = JSON.parseObject(body, Map.class);


        // 解析支付结果
        PayStatusDto payStatusDto = new PayStatusDto();
        payStatusDto.setOut_trade_no(payNo);
        payStatusDto.setTrade_no(bodyMap.get("trade_no"));
        payStatusDto.setTrade_status(bodyMap.get("trade_status"));
        payStatusDto.setApp_id(APP_ID);
        payStatusDto.setTotal_amount(bodyMap.get("total_amount"));

        return payStatusDto;
    }

    /**
     * 保存支付宝支付结果
     *
     * @param payStatusDto 支付结果信息(从支付宝查询到的信息)
     */
    @Transactional
    public void saveAliPayStatus(PayStatusDto payStatusDto) {
        // 获取支付记录号
        String payNO = payStatusDto.getOut_trade_no();

        // 查询支付记录信息
        XcPayRecord payRecordByPayNo = getPayRecordByPayNo(payNO);
        if (payRecordByPayNo == null) {
            XueChengPlusException.cast("支付记录不存在");
        }

        // 拿到相关连的订单id
        Long orderId = payRecordByPayNo.getOrderId();
        XcOrders xcOrders = ordersMapper.selectById(orderId);
        if (xcOrders == null) {
            XueChengPlusException.cast("订单不存在");
        }

        // 获取数据库中的支付状态
        String statusFromDb = payRecordByPayNo.getStatus();
        // 如果数据库中的支付状态已经是成功了，不在处理
        if ("601002".equals(statusFromDb)) {
            return;
        }

        // 如果支付成功
        String tradeStatus = payStatusDto.getTrade_status(); // 从支付宝查询到的支付结果
        if ("TRADE_SUCCESS".equals(tradeStatus)) { // 支付宝返回的信息为支付成功
            // 更新支付记录表的状态为支付成功
            payRecordByPayNo.setStatus("601002");
            // 支付宝的订单号
            payRecordByPayNo.setOutPayNo(payStatusDto.getTrade_no());
            // 第三方支付渠道编号
            payRecordByPayNo.setOutPayChannel("Alipay");
            // 支付成功时间
            payRecordByPayNo.setPaySuccessTime(LocalDateTime.now());
            // 更新数据
            payRecordMapper.updateById(payRecordByPayNo);

            // 更新订单表的状态为支付成功
            xcOrders.setStatus("600002");
            ordersMapper.updateById(xcOrders);

            // 将消息写道数据库
            MqMessage mqMessage = mqMessageService.addMessage("payresult_notify", xcOrders.getOutBusinessId(), xcOrders.getOrderType(), null);

            // 发送消息
            notifyPayResult(mqMessage);
        }
    }

    /**
     * 发送通知结果
     *
     * @param message 消息对象
     */
    @Override
    public void notifyPayResult(MqMessage message) {
        // 获取消息内容
        String jsonString = JSON.toJSONString(message);
        // 创建一个持久化消息
        Message messageObj = MessageBuilder.withBody(jsonString.getBytes(StandardCharsets.UTF_8)).setDeliveryMode(MessageDeliveryMode.PERSISTENT).build();

        // 消息id
        Long id = message.getId();
        // 全局消息id
        CorrelationData correlationData = new CorrelationData(id.toString());
        // 使用correlationData指定回调方法
        correlationData.getFuture().addCallback(result -> { // 成功回调
            if (result.isAck()) {
                // 消息发送成功到交换机
                log.debug("消息发送成功到交换机,消息id:{},消息内容:{}", id, jsonString);
                // 将消息从数据库表中删除
                mqMessageService.completed(id);
            } else {
                // 消息发送失败
                log.debug("消息发送失败到交换机,消息id:{},消息内容:{}", id, jsonString);
            }
        }, ex -> {
            // 发生异常的回调
            log.debug("消息发送失败到交换机,消息id:{},消息内容:{},异常信息:{}", id, jsonString, ex.getMessage());
        });
        // 发送消息
        rabbitTemplate.convertAndSend(PayNotifyConfig.PAYNOTIFY_EXCHANGE_FANOUT, "", messageObj, correlationData);
    }

    /**
     * 保存支付记录
     *
     * @param orders 订单信息
     * @return 支付记录对象
     */
    public XcPayRecord createPayRecord(XcOrders orders) {
        // 获取订单id
        Long orderId = orders.getId();
        // 查询订单数据
        XcOrders xcOrders = ordersMapper.selectById(orderId);
        // 如果此订单不存在就不添加支付记录
        if (xcOrders == null) {
            XueChengPlusException.cast("订单不存在");
        }

        // 获取订单的状态
        String status = xcOrders.getStatus();
        // 如果此订单的支付结果为成功，就不在添加支付记录，防止重复记录
        if ("601002".equals(status)) {
            // 说明订单已经支付
            XueChengPlusException.cast("订单已支付，请勿重复支付");
        }

        XcPayRecord payRecord = new XcPayRecord();
        // 生成支付交易流水号
        long payNo = IdWorkerUtils.getInstance().nextId();
        payRecord.setPayNo(payNo);
        payRecord.setOrderId(orders.getId()); // 商品订单号
        payRecord.setOrderName(orders.getOrderName());
        payRecord.setTotalPrice(orders.getTotalPrice());
        payRecord.setCurrency("CNY");
        payRecord.setCreateDate(LocalDateTime.now());
        payRecord.setStatus("601001"); // 未支付
        payRecord.setUserId(orders.getUserId());

        // 插入数据
        int insert = payRecordMapper.insert(payRecord);
        if (insert <= 0) {
            XueChengPlusException.cast("添加支付记录失败");
        }

        return payRecord;
    }

    /**
     * 保存订单信息
     *
     * @param userId      用户id
     * @param addOrderDto 订单信息
     * @return
     */
    public XcOrders saveXcOrders(String userId, AddOrderDto addOrderDto) {
        // 进行幂等性判断，同一个选课记录只能有一个订单
        XcOrders xcOrders = getOrderByBusinessId(addOrderDto.getOutBusinessId());
        if (xcOrders != null) {
            // 说明已经存在了
            return xcOrders;
        }

        // 插入订单主表
        // 生成订单号
        long orderId = IdWorkerUtils.getInstance().nextId(); // 使用雪花算法生成订单id
        xcOrders.setId(orderId);
        xcOrders.setTotalPrice(addOrderDto.getTotalPrice());
        xcOrders.setCreateDate(LocalDateTime.now());
        xcOrders.setStatus("600001"); // 未支付
        xcOrders.setUserId(userId);
        xcOrders.setOrderType(addOrderDto.getOrderType());
        xcOrders.setOrderName(addOrderDto.getOrderName());
        xcOrders.setOrderDetail(addOrderDto.getOrderDetail());
        xcOrders.setOrderDescrip(addOrderDto.getOrderDescrip());
        xcOrders.setOutBusinessId(addOrderDto.getOutBusinessId()); // 如果是选课这里记录选课的id
        // 插入数据
        int insert = ordersMapper.insert(xcOrders);
        if (insert <= 0) {
            XueChengPlusException.cast("添加订单信息失败");
        }

        // 订单id 使用的是mp自带的主键返回
        Long id = xcOrders.getId();

        // 插入订单明细表
        // 将前端传递的订单明细json串转成list
        String orderDetailJson = addOrderDto.getOrderDetail();
        List<XcOrdersGoods> xcOrdersGoods = JSON.parseArray(orderDetailJson, XcOrdersGoods.class);
        // 遍历订单明细插入订单明细表
        xcOrdersGoods.forEach(goods -> {
            // 订单id
            goods.setId(id);
            // 插入订单明细
            ordersGoodsMapper.insert(goods);
        });

        return xcOrders;
    }

    /**
     * 根据业务id查询订单 , 业务id是选课记录表中的主键
     *
     * @param businessId 业务id
     * @return 订单信息
     */
    public XcOrders getOrderByBusinessId(String businessId) {
        return ordersMapper.selectOne(new LambdaQueryWrapper<XcOrders>().eq(XcOrders::getOutBusinessId, businessId));
    }
}
